home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 November: Tool Chest / Dev.CD Nov 98 TC.toast / Tool Chest / Sound / SoundApp / SoundUnit.c < prev    next >
Encoding:
Text File  |  1997-02-18  |  53.6 KB  |  1,645 lines  |  [TEXT/CWIE]

  1. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2. /*
  3. Apple Macintosh Developer Technical Support
  4.  
  5. SoundUnit
  6.  
  7. SoundUnit.c    - C Source
  8.  
  9. Versions:
  10.         1.03                January, 1990
  11.         1.04                Sept, 1990
  12.         1.2                    August, 1994        translated to C
  13.  
  14. Components:
  15.         SoundApp.c            January, 1990        MPW C source code
  16.         SoundUnit.c            January, 1990        MPW C source code
  17.         SoundUnit.h            January, 1990        MPW C source code
  18.         SoundApp.r            January, 1990        MPW Rez source code
  19.         SoundAppSnds.r        January, 1990        MPW Rez source code
  20.         SoundApp.make        January, 1990        MPW build script
  21.  
  22. Formatting was done with FONT = Courier or Monaco, SIZE = 10, TABS = 4
  23.  
  24. Version comments
  25.  
  26. 1.1:  This is the "new" SoundUnit which adds some new features.
  27.         • Some knowledge of the new Sound Manager is present
  28.           in areas that were work-arounds for old Sound Manager bugs
  29.         • Conversion to MPW 3.2 was established (with some amount of pain)
  30.         • Added the new Sound Manager error strings
  31.         • Checking of the sound header for supported encode values
  32.         • The amp value is ignored (never used) in a freqDurationCmd
  33.         • Added functions to test sound hardware/software features
  34.           such as stereo, MACE, Sound Input
  35.  
  36.  
  37. Formatting was done with FONT = Courier, SIZE = 10, TABS = 4
  38.  
  39.  
  40. SoundApp.c is a sample application source file for demonstrating the
  41. Sound Manager.  This portion of the source code handles the Sound Manager
  42. part of the application.  This source can be used by others.
  43.  
  44. Jim Reekes
  45. Sunday, August 7, 1994 7:06:41 PM
  46. */
  47. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  48.  
  49. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  50. //includes
  51. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  52.  
  53. #include <Errors.h>
  54. #include <FixMath.h>
  55. #include <fp.h>
  56. #include <Gestalt.h>
  57. #include <LowMem.h>
  58. #include <Memory.h>
  59. #include <MixedMode.h>
  60. #include <Resources.h>
  61.  
  62. #include <limits.h>
  63. #include <stddef.h>
  64.  
  65. #include <Sound.h>
  66. #include <SoundInput.h>
  67. #include "SoundUnit.h"
  68.  
  69. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  70. // private constants
  71. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  72.  
  73. enum {
  74.     kMaxChannels    = 4,                    //maximum number of supported channels
  75.  
  76.     kSoundComplete    = 0x1234                //flag for callBackCmd
  77. };
  78.  
  79. /*
  80. These are used as flags in the sound channel to determine the state
  81. of that channel.
  82. */
  83. enum {
  84.     kChanFreeState    = 0,                //channel is not in use
  85.     kChanCompleteState                    //channel has completed
  86. };
  87.  
  88. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  89. // macros
  90. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  91.  
  92. #if GENERATINGCFM
  93. #define CreateRoutineDescriptor(info, proc)                                    \
  94.  RoutineDescriptor g##proc##RD = BUILD_ROUTINE_DESCRIPTOR(info, proc)
  95.  
  96. #define GetRoutineAddress(proc)    (&g##proc##RD)
  97.  
  98. #else
  99. #define GetRoutineAddress(proc)    proc
  100. #endif
  101.  
  102. // this belongs in LowMem.h
  103. extern unsigned short LMGetSoundActive( void )
  104.     TWOWORDINLINE( 0x1038, 0x027E ); /* MOVE.B $027E, D0 */
  105.  
  106. // For Power Macs, there is no Sound Driver and therefore SoundActive in
  107. // low memory should not be a problem. This is sometimes set by third parties
  108. // that are writting directly to the hardware, and also set by the old
  109. // sound driver when it is active. When this is happening, the Sound Manager
  110. // cannot work. So we check the low memory global to be less than 0. But
  111. // for Power Mac builds, we just return 0.
  112.  
  113. #if USESROUTINEDESCRIPTORS
  114. #define SoundDriverActive() false
  115. #else
  116. #define SoundDriverActive() (LMGetSoundActive() < 0)
  117. #endif
  118.  
  119. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  120. // private types
  121. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  122.  
  123. /*
  124. This is an element of a globals array that is used to keep track of
  125. sound channels created by the sound unit.  It contains all the useful
  126. information associated with the channel.  I keep the 'snd ' resource
  127. handle associated to this channel too.  This allows me to dispose of
  128. the data once the channel has completed its duties.
  129. */
  130.  
  131. struct ChanInfo {
  132.     SndChannelPtr    chan;
  133.     SndListHandle    dataHandle;
  134.     short            chanState;
  135.     short            chanType;
  136. };
  137. typedef struct ChanInfo ChanInfo;
  138. typedef ChanInfo *ChanInfoPtr;
  139.  
  140.  
  141. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  142. // private prototypes
  143. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  144.  
  145. void FreeChan(ChanInfoPtr info);
  146. OSErr ChanAvailable(ChanInfoPtr info);
  147. Boolean CompatibleChan(ChanInfoPtr info);
  148. OSErr InstallSampleSnd(ChanInfoPtr info, SndListHandle sndHandle);
  149. OSErr NewWaveChan(ChanInfoPtr info, short init);
  150.  
  151. pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd);
  152. Boolean IsMyChan(SndChannelPtr chan);
  153. OSErr SndDataAvailable(SndListHandle sndHandle);
  154. ModRef GetSynthInfo(SndListHandle sndHandle);
  155. Boolean SupportedSH(SoundHeaderPtr sndPtr);
  156. OSErr ReleaseSynch(SndChannelPtr chan);
  157. OSErr Synch1Chan(SndChannelPtr chan, short count);
  158. OSErr SynchChans(SndChannelPtr chan1, SndChannelPtr chan2, SndChannelPtr chan3, SndChannelPtr chan4);
  159.  
  160.  
  161. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  162. //globals (The “g” prefix is used to emphasize that a variable is global.)
  163. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  164.  
  165. /*
  166. This is the global array of sound channel information.
  167. */
  168.  
  169.     ChanInfoPtr        gChanInfo;
  170.  
  171. /*
  172. gSoundMgrVersion is used to determine if the application is running
  173. with the old Sound Manager.  This was shipped prior to System 6.0.6.
  174. This flag is setup in the InitSoundUnit routine and used by the rest of this
  175. source file. There are workarounds to problems based on this condition.
  176. */
  177.     short            gSoundMgrVersion;
  178.  
  179. // allocate the RoutineDescriptors for Power Mac toolbox calls
  180. #if GENERATINGCFM
  181. CreateRoutineDescriptor(uppSndCallBackProcInfo, DoCallBack);
  182. #endif
  183.  
  184. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  185. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  186. /*
  187. This is a dummy routine to allow the application to unload this segment.
  188. */
  189.  
  190. #pragma segment SoundUnit
  191. pascal void _SoundUnit(void)
  192. {
  193. }
  194.  
  195. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  196. /*
  197. Create storage for each of the four channels (4 * 1064 bytes) and
  198. initialize them.  If any of the channels cannot be allocated, return an
  199. error.  If the user only wants one channel, then we could just allocate
  200. one instead of four.  These channels are used at interrupt time.
  201.  
  202. VERSION 1.1: Added the new Sound Manager flag, gNewSndMgr.  First I have to
  203. test if the _SoundDispatch trap is available, since SndManagerVersion is
  204. a selector for _SoundDispatch.  This this trap isn't available, then calling
  205. SndManagerVersion would result in an unimplemented instruction.  If an error
  206. is returned, it is the old Sound Manager.  If it is zero, then the call
  207. is available (via-MIDI Mgr?) but it isn't the new Sound Manager.  Greater
  208. than zero means it is the new Sound Manager.
  209.  
  210. VERSION 1.2: Only supports Sound Manager 2.0 or later. I would personally
  211. require version 3.0 or later in my own products.
  212. */
  213.  
  214. #pragma segment Initialize
  215. pascal OSErr InitSoundUnit(void)
  216. {
  217.     OSErr        theErr;
  218.  
  219.     // check if the supported Sound Manager is present, this is the one
  220.     // that has Sound Input and supports the _SoundDispatch trap
  221.  
  222.     theErr = noErr;
  223.     gSoundMgrVersion = GetSoundMgrVersion();
  224.     if (gSoundMgrVersion > 1)
  225.     {
  226.         gChanInfo = (ChanInfoPtr)NewPtrClear(sizeof(ChanInfo[kMaxChannels]));
  227.         if (gChanInfo == nil)
  228.             theErr = MemError();
  229.     }
  230.     return(theErr);
  231. }
  232.  
  233. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  234. /*
  235. To determine if MACE is available, there are two case.  If the new Sound Manager
  236. is running then MACE may be built in.  This is easy enough to test for by calling
  237. the new trap call.  Otherwise I have to test for the presence of the MACE
  238. snth resources.  The old MACE snths could only be present if the user ran the
  239. MACE Installer Scripts which are available from APDA. This was only supported
  240. on some versions of System 6.0.x.
  241.  
  242. VERSION 1.2: Forget about dealing with MACE prior to Sound Manager 2, it was
  243. just a hack and didn't work on all machines.
  244. */
  245.  
  246. #pragma segment Main
  247. pascal Boolean HasMACE(void)
  248. {
  249.     NumVersion version;
  250.     Boolean result;
  251.  
  252.     result = false;
  253.     if (GetSoundMgrVersion() > 1)
  254.     {
  255.         version = MACEVersion();
  256.         //result = (version.majorRev > 0);            //is the built-in MACE here?
  257.         
  258.         result = (*(UInt8*)&version > 0);            // added by MW  (see comments, above)
  259.     }
  260.     return (result);
  261. }
  262.  
  263. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  264. /*
  265. This is used to determine if Sound Input is available. The Gestalt flag
  266. gestaltHasSoundInputDevice can be used to determine if an input device is
  267. available. This flag did not exist prior to System 7. The other related
  268. flag gestaltBuiltInSoundInput can only be used to determine if the machine
  269. has built-in sound input hardware, so don't be mislead. In System 6.0.7 you
  270. would have to use SPBGetIndexdDevice to find the fist one.  If this returns
  271. noErr then Sound Input is available. Also, the icon handle returned by this
  272. call has to be disposed of by you the caller.
  273.  
  274. VERSION 1.1:  This is the external routine for users to determine if
  275. sound input is available.
  276.  
  277. VERSION 1.2: Use the Gestalt method since we know that we're running Sound
  278. Manager 2 or later which supports the gestaltHasSoundInputDevice flag.
  279. */
  280.  
  281. #pragma segment Main
  282. pascal Boolean HasSoundInput(void)
  283. {
  284.     long        response;
  285.     OSErr        theErr;
  286.     Boolean        result;
  287.  
  288.     theErr = Gestalt(gestaltSoundAttr, &response);
  289.     if ( (theErr == noErr) && (response & (1<<gestaltHasSoundInputDevice)) )
  290.         result = true;
  291.     else
  292.         result = false;
  293.  
  294.     return (result);
  295. }
  296.  
  297. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  298. /*
  299. VERSION 1.1:  This is the external routine for users to determine if
  300. sound input is available. Sound Manager 3.0 can always support stereo,
  301. even on mono hardware.
  302. */
  303.  
  304. #pragma segment Main
  305. pascal Boolean HasStereoSupport(void)
  306. {
  307.     long response;
  308.     OSErr theErr;
  309.     Boolean result;
  310.  
  311.     theErr = Gestalt(gestaltSoundAttr, &response);
  312.     if ( (theErr == noErr) && (response & (1<<gestaltStereoCapability)) )
  313.         result = true;
  314.     else
  315.         result = false;
  316.     return (result);
  317. }
  318.  
  319. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  320. /*
  321. This will return the version of the sound manager that is currently running.
  322. A special case is necessary because the first sound manager that reported
  323. its version didn't have the SndSoundManagerVersion() implemented. So that means
  324. older versions are really 1.0, and the first oldest version returned by
  325. SndSoundManagerVersion is 2.0. Anything older than 2.0 has a few problems.
  326. */
  327.  
  328. pascal short GetSoundMgrVersion(void)
  329. {
  330.     NumVersion version;
  331.     long response;
  332.     short result;
  333.     OSErr theErr;
  334.  
  335.     result = 1;
  336.     theErr = Gestalt(gestaltSoundAttr, &response);
  337.     if ( (theErr == noErr) && (response & (1<<gestaltSoundIOMgrPresent)) )
  338.     {
  339.         version = SndSoundManagerVersion();
  340.         //result = version.majorRev;
  341.         
  342.         result = *(UInt8*)&version;            // added by MW (see comments, above)
  343.     }
  344.     return(result);
  345. }
  346.  
  347. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  348. /*
  349. This routine can be called to determine if the sound has completed.  When
  350. this is true, the data can be disposed of.  It is set by the sound
  351. channel's completion routine.  It is placed in the Main segment because
  352. it is called by the event loop.
  353. */
  354.  
  355. #pragma segment Main
  356. pascal Boolean HasSoundCompleted(void)
  357. {
  358.     short        i;
  359.     Boolean        result;
  360.  
  361.     result = true;                            //assume true, then check for busy channels
  362.     for (i = 0; i < kMaxChannels; i++)
  363.     {
  364.         // if we have a channel and it is not completed, then we're still busy playing
  365.         if ((gChanInfo[i].chan != nil) && (gChanInfo[i].chanState != kChanCompleteState))
  366.         {
  367.             result = false;
  368.             break;
  369.         }
  370.     }
  371.     return(result);
  372. }
  373.  
  374. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  375. /*
  376. This routine can be called at any time.  It will return true when the
  377. SoundUnit has an open channel.  This can be can considered the same as
  378. sound being active.  As long as the channel is open, no other channels can
  379. be opened.  It is placed in the Main segment because it is called by the
  380. event loop.
  381. */
  382.  
  383. #pragma segment Main
  384. pascal Boolean HasChannelOpen(void)
  385. {
  386.     short        i;
  387.     Boolean        result;
  388.  
  389.     result = false;
  390.     for (i = 0; i < kMaxChannels; i++)
  391.     {
  392.         if (gChanInfo[i].chan != nil)
  393.         {
  394.             result = true;
  395.             break;
  396.         }
  397.     }
  398.  
  399.     return(result);
  400. }
  401.  
  402. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  403. /*
  404. Given a channel and timbre(sounds like "tom burr"), this will adjust the
  405. tone quality of the square wave synthesizer.  Changing the tone can only be done
  406. before playing a square wave.  On a Mac with the Apple Sound Chip, this can be
  407. done in real time while a note is playing.  But, since there's no
  408. supported method for determining if the ASC is available I have to assume
  409. that it's not.  I use the immediate flag to determine if the user wants to
  410. change the timbre now, or queue the command.  If the queue is full, it
  411. will wait for the command to be accepted.
  412.  
  413. BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
  414. SE where sending a timbreCmd with a timbre of 255(a legal value)will
  415. crash.  The difference between 254 and 255 isn't audible, so I only allow
  416. a maximum of 254 in any case.
  417. */
  418.  
  419. #pragma segment SoundUnit
  420. pascal OSErr SetSquareWaveTimbre(SndChannelPtr squareChan, short timbre, Boolean immediate)
  421. {
  422.     SndCommand theCmd;
  423.     OSErr      result;
  424.  
  425.     if (timbre > 254)
  426.         timbre = 254;
  427.  
  428.     theCmd.cmd = timbreCmd;
  429.     theCmd.param1 = timbre;
  430.     theCmd.param2 = 0;
  431.  
  432.     if (immediate)
  433.         result = SndDoImmediate(squareChan, &theCmd);
  434.     else
  435.         result = SndDoCommand(squareChan, &theCmd, kWait);
  436.  
  437.     return(result);
  438. }
  439.  
  440. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  441. /*
  442. Given a channel and note information, this will place the note into the
  443. channel's queue.  The note contains the amplitude and note value.  It is a
  444. four byte parameter with the high byte containing the amplitude.  I use
  445. SndDoCommand with the noWait flag set to wait for the channel to except
  446. the command in the case the queue is currently full.
  447. */
  448.  
  449. #pragma segment SoundUnit
  450. pascal OSErr SendNote(SndChannelPtr chan, short duration, long note)
  451. {
  452.     SndCommand theCmd;
  453.  
  454.     theCmd.cmd = freqDurationCmd;
  455.     theCmd.param1 = duration;
  456.     theCmd.param2 = note;
  457.  
  458.     return(SndDoCommand(chan, &theCmd, kWait));
  459. }
  460.  
  461. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  462. /*
  463. Given a channel, this will place a quietCmd into the channel's queue.  I
  464. use SndDoCommand with the noWait flag set to wait for the channel to
  465. except the command in the case it is currently full.
  466.  
  467. BUG NOTE: A sequence of notes and rests will not work unless quietCmds
  468. are between them.  Rests have to be made quiet before they rest, if that
  469. makes any more sense.  A freqDurationCmd will loop, causing the sound in progress
  470. to continue, until a quietCmd is received.
  471. */
  472.  
  473. #pragma segment SoundUnit
  474. pascal OSErr SendQuiet(SndChannelPtr chan, Boolean immediate)
  475. {
  476.     SndCommand theCmd;
  477.     OSErr      result;
  478.  
  479.     theCmd.cmd = quietCmd;
  480.     theCmd.param1 = 0;
  481.     theCmd.param2 = 0;
  482.  
  483.     if (immediate)
  484.         result = SndDoImmediate(chan, &theCmd);
  485.     else
  486.         result = SndDoCommand(chan, &theCmd, kWait);
  487.  
  488.     return(result);
  489. }
  490.  
  491. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  492. /*
  493. Given a channel and duration, this will place the rest into the channel's
  494. queue.  Before sending a rest a quietCmd is needed.  Rests don't work
  495. unless you tell the Sound Manager to be quiet too.  I use SndDoCommand
  496. with the noWait flag set to wait for the channel to except the command in
  497. the case it is currently full.
  498. */
  499.  
  500. #pragma segment SoundUnit
  501. pascal OSErr SendRest(SndChannelPtr chan, short duration)
  502. {
  503.     SndCommand theCmd;
  504.     OSErr      theErr;
  505.  
  506.     theErr = SendQuiet(chan, kWait);
  507.  
  508.     if (theErr == noErr) {
  509.         theCmd.cmd = restCmd;
  510.         theCmd.param1 = duration;
  511.         theCmd.param2 = 0;
  512.  
  513.         theErr = SndDoCommand(chan, &theCmd, kWait);
  514.     }
  515.  
  516.     return(theErr);
  517. }
  518.  
  519. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  520. /*
  521. Test if the channel is free, and if not then call SndDisposeChannel.
  522. This will release the synthesizer(snth resource)code and the
  523. required hardware.  If we didn't do this, no other channels would
  524. work.  I also test myChan for having a snd resource attached to the
  525. channel.  If so, then I mark it as purgeable and reset the data to nil.
  526.  
  527. BUG NOTE: Calling SndDisposeChannel while or immediately after playing
  528. a sequence of notes would often hang/crash a non-Apple Sound Chip based Mac.
  529. Issuing a quietCmd first kept the Sound Manager happy and my Mac from
  530. crashing.
  531. */
  532.  
  533. #pragma segment SoundUnit
  534. void FreeChan(ChanInfoPtr info)
  535. {
  536.     OSErr      theErr;
  537.  
  538.     if (info->chan != nil) {
  539.         theErr = SendQuiet(info->chan, !kWait);  // ignore error
  540.         theErr = SndDisposeChannel(info->chan, !kWait);
  541.         info->chan = nil;
  542.         info->chanState = kChanFreeState;
  543.     }
  544.  
  545.     if (info->dataHandle != nil) {
  546.         HUnlock((Handle)info->dataHandle);
  547.         HPurge((Handle)info->dataHandle);
  548.         info->dataHandle = nil;
  549.     }
  550. }
  551.  
  552. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  553. /*
  554. This routine is called by an application that established a sound to be
  555. played asynchronously.  This is in effect, the routine to be used after
  556. the completion routine has been called.  The application should call this
  557. once HasSoundCompleted() returns true.  In the case the application is using
  558. multiple channels, we will only free a channel once it has been marked as
  559. kChanCompleteState.
  560. */
  561.  
  562. #pragma segment SoundUnit
  563. pascal void DoSoundComplete(void)
  564. {
  565.     short        i;
  566.  
  567.     for (i = 0; i < kMaxChannels; i++)
  568.     {
  569.         if (gChanInfo[i].chanState != kChanFreeState)
  570.             FreeChan(&gChanInfo[i]);
  571.     }
  572. }
  573.  
  574. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  575. /*
  576. This is the routine that will force all channels to be released  This is used by
  577. all routines just before opening a new channel to force all channels to be disposed.
  578. */
  579.  
  580. #pragma segment SoundUnit
  581. pascal void FreeAllChans(void)
  582. {
  583.     short        i;
  584.  
  585.     for (i = 0; i < kMaxChannels; i++)
  586.         FreeChan(&gChanInfo[i]);
  587. }
  588.  
  589. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  590. /*
  591. This is the final routine to be called by the application when it is has
  592. finished using this SoundUnit.  This will dispose of all the channels and
  593. memory used by this SoundUnit.
  594. */
  595.  
  596. #pragma segment SoundUnit
  597. pascal void FreeSoundUnit(void)
  598. {
  599.     FreeAllChans();
  600.     DisposPtr((Ptr)gChanInfo);
  601. }
  602.  
  603. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  604. /*
  605. This will be called at interrupt time by the Sound Manager when it
  606. receives a callBackCmd.  I use the second parameter of the command to hold
  607. my application's A5 reference.  I first set up A5 so that I can access my
  608. globals.  I mark the given channel as being complete.  This lets the
  609. application know that the callBackCmd has been processed.  The callBackCmd
  610. can be used for other purposes, and the first parameter of the command
  611. could be a flag to a more extensive routine.  Synchronizing the application
  612. with the channel is possible with this method.
  613.  
  614. WARNING: This routine MUST be resident in memory and cannot make a call
  615. to a non-resident segment.  I put this into the Main segment because of
  616. this.
  617.  
  618. BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
  619. 'snd '.  A bogus callBackCmd is placed into the queue immediately after
  620. the bufferCmd used to play the sound.  This bogus callBackCmd will cause
  621. my callBackProc to be called when I wasn't expecting it.  I have been
  622. using the command's second parameter to contain my A5 address.  If I'm
  623. given a bogus callBackCmd, it would be really bad to set A5 address to
  624. this bogus parameter in the command.  I found that the bogus callBackCmd
  625. contains the handle to the 'snd ' passed in to _SndPlay.  I also found
  626. that param1 contains the handle's state bits(results of HGetState).  To
  627. work with this bug I set my real callBackCmd's param1 to a specific value
  628. when I installed it into the queue.  See the SoundComplete routine.  Then
  629. I test the callBackCmd to make sure I'm dealing with the real one.
  630.  
  631. VERSION 1.2: No longer using A5 globals.  Get the address that we need
  632. in param2, instead of passing in our application's A5 address.
  633. */
  634.  
  635. #pragma segment Main
  636. pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd)
  637. {
  638. #pragma unused (chan)
  639.     ChanInfoPtr        info;
  640.  
  641.     if (theCmd->param1 == kSoundComplete)         // if it's my callBackCmd
  642.     {
  643.         info = (ChanInfoPtr)theCmd->param2;
  644.         info->chanState = kChanCompleteState;    // this channel is done
  645.     }
  646. }
  647.  
  648. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  649. /*
  650. I use this to install the callBackCmd into the given channel.  I need to
  651. pass in our A5 along with the command so that the callBack routine can
  652. access my globals.  I also wait until the channel is ready for another
  653. command in the case of the channel being full.  Once the Sound Manager
  654. calls my call back procedure I will dispose of the channel.  So, this is
  655. the last sound command to be sent to a channel.  I pass to the call back
  656. A5 in the second parameter of the callBackCmd.  Refer to Tech Note #208.
  657.  
  658. VERSION 1.2: No longer using A5 globals.  Get the address that we need
  659. in param2, instead of passing in our application's A5 address.
  660. */
  661.  
  662. #pragma segment SoundUnit
  663. pascal OSErr SoundComplete(SndChannelPtr chan)
  664. {
  665.     SndCommand    theCmd;
  666.     OSErr        result;
  667.     short        i;
  668.  
  669.     theCmd.cmd = callBackCmd;
  670.     theCmd.param1 = kSoundComplete;
  671.     theCmd.param2 = 0;                            // initialize value
  672.  
  673.     for (i = 0; i < kMaxChannels; i++)
  674.     {
  675.         if (gChanInfo[i].chan == chan)
  676.         {
  677.             theCmd.param2 = (long)(&gChanInfo[i]);
  678.             break;
  679.         }
  680.     }
  681.     if (theCmd.param2 == 0)
  682.         result = badChannel;                    // channel wasn't one of ours
  683.     else
  684.         result = SndDoCommand(chan, &theCmd, kWait);
  685.  
  686.     return(result);
  687. }
  688.  
  689. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  690. /*
  691. This routine will test the given channel to see if it will really produce
  692. sound.  The Sound Manager in System 6.0x will return noErr even if the
  693. channel isn't going to work.  In the future the Sound Manager will return
  694. the proper error.  Until then, I use this routine to determine this for me.
  695. There can only be a single channel at any time, unless I have the wave
  696. table synthesizer open.  This will allow four channels.  Channels have
  697. a pointer to the next channel, and if this is not nil I suspect the
  698. given channel will not work.  I test the given channel for being a
  699. wave type, and if so I need to see if the other channels I've got are
  700. also wave type.  If it doesn't look like the channels is available, I
  701. return badChannel.
  702.  
  703. This routine assumes the channel passed in is the first channel allocated.
  704. Channels are held in a linked list, and the first one to be tested has
  705. to be the first one allocated.
  706.  
  707. BUG NOTE: If an application is not using the Sound Manager and instead
  708. uses the older Sound Driver, any given channel will fail.  Or if the other
  709. application does not release is channels, then my channels will not work.
  710. The most noticeable offender of this is HyperCard.  Friendly applications
  711. will dispose of their channels at suspend/resume times or ASAP.
  712.  
  713. VERSION 1.1: Added the new Sound Manager test.  The new Sound Manager will
  714. allow multiple sound channels, and returns proper error codes.
  715.  
  716. VERSION 1.2: This is not used since we only support Sound Manager 2.0 or later.
  717. */
  718.  
  719. #pragma segment SoundUnit
  720. OSErr ChanAvailable(ChanInfoPtr info)
  721. {
  722.     OSErr        result;
  723.     short        i;
  724.  
  725.     if (gSoundMgrVersion >= 3)
  726.         return(noErr);
  727.  
  728.     result = noErr;
  729.  
  730.     if (SoundDriverActive())                        // check for sound driver
  731.         return(notEnoughHardwareErr);
  732.  
  733.     if (info->chan->nextChan != nil)                // looks bad
  734.     {
  735.         result = badChannel;                          // prepart to fail
  736.         if (info->chanType == waveTableSynth)        // last attempt
  737.         {
  738.             if (IsMyChan(info->chan->nextChan))
  739.             {
  740.                 for (i = 0; i < kMaxChannels; i++)
  741.                 {
  742.                     if (! (CompatibleChan(&gChanInfo[i])))
  743.                         break;
  744.                     result = noErr;                  // got lucky
  745.                 }
  746.             }
  747.         }
  748.     }
  749.     return(result);
  750. }
  751.  
  752. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  753. /*
  754. This is a utility routine that works with ChanAvailable.  It checks to
  755. see if the given channel is one of the four channels we opened.  If it
  756. is, IsMyChan returns true.  If it isn't, false is returned.
  757. */
  758.  
  759. #pragma segment SoundUnit
  760. Boolean IsMyChan(SndChannelPtr chan)
  761. {
  762.     short        i;
  763.     Boolean        result;
  764.  
  765.     result = false;
  766.     for (i = 0; i < kMaxChannels; i++)
  767.     {
  768.         if (gChanInfo[i].chan == chan)
  769.         {
  770.             result = true;
  771.             break;
  772.         }
  773.     }
  774.     return(result);
  775. }
  776.  
  777. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  778. /*
  779. This is a utility routine that works with ChanAvailable.  It returns
  780. true if the given channel is either a wave type of channel.  It also
  781. returns true if the channel is free.  Otherwise, it returns false.
  782. */
  783.  
  784. #pragma segment SoundUnit
  785. Boolean CompatibleChan(ChanInfoPtr info)
  786. {
  787.     Boolean        result;
  788.  
  789.     if (    (info->chanType == waveTableSynth)        // wave or..
  790.         ||    (info->chanState == kChanFreeState) )    // free chan
  791.  
  792.         result = true;
  793.     else
  794.         result = false;
  795.  
  796.     return(result);
  797. }
  798.  
  799. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  800. /*
  801. Given a resource handle, this will attempt to load it into memory.  If the
  802. data is not available, then return an error.
  803. */
  804.  
  805. #pragma segment SoundUnit
  806. OSErr SndDataAvailable(SndListHandle sndHandle)
  807. {
  808.     OSErr result;
  809.  
  810.     result = noErr;
  811.     if (sndHandle != nil) {
  812.         LoadResource((Handle)sndHandle);
  813.         if (*sndHandle == nil)
  814.             result = nilHandleErr;            // master pointer is nil
  815.     }
  816.     else
  817.         result = nilHandleErr;                // user passed a nil handle
  818.     return(result);
  819. }
  820.  
  821. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  822. /*
  823. This is used to put the given sound resource into memory and hold it there.
  824. I also use a MoveHHi to keep the heap from being fragmented.  If this
  825. fails, then I return an error.  I dereference the handle and check if the
  826. master pointer is nil.  This would mean the data could not be loaded.
  827. */
  828.  
  829. #pragma segment SoundUnit
  830. pascal OSErr HoldSnd(SndListHandle sndHandle)
  831. {
  832.     OSErr result;
  833.  
  834.     if (sndHandle != nil) {
  835.         LoadResource((Handle)sndHandle);
  836.         if (*sndHandle == nil)
  837.             result = nilHandleErr;            // master pointer is nil
  838.         else {
  839.             result = noErr;
  840.             HLockHi((Handle)sndHandle);
  841.         }
  842.     }
  843.     else
  844.         result = nilHandleErr;                // user passed a nil handle
  845.     return(result);
  846. }
  847.  
  848. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  849. /*
  850. This routine will return the 'snth' resource ID specified by the sound.
  851. I use this to determine if the given sound will work with _SndPlay.
  852. This routine does not require the data to be in memory when called.  It
  853. also doesn't lock it down while looking for the information.
  854.  
  855. VERSION 1.1:  If no synth information is found in the snd then by default
  856. it is assumed to be for the squareWaveSynth.
  857. */
  858.  
  859. #pragma segment SoundUnit
  860. ModRef GetSynthInfo(SndListHandle sndHandle)
  861. {
  862.     SndListPtr        soundPtr;
  863.     OSErr            theErr;
  864.     ModRef            currSynth;
  865.  
  866.     currSynth.modNumber = kNoSynth;            //initialize to no synth, and no init
  867.     currSynth.modInit = 0;
  868.     theErr = SndDataAvailable(sndHandle);
  869.     if (theErr == noErr)
  870.     {
  871.         soundPtr = (*sndHandle);
  872.         if (soundPtr->format == firstSoundFormat)
  873.         {
  874.             if (soundPtr->numModifiers != 0)
  875.                 currSynth = soundPtr->modifierPart[0];
  876.             else
  877.                 currSynth.modNumber = squareWaveSynth;
  878.         }
  879.         else                                //snd is a format 2 for HyperCard
  880.             currSynth.modNumber = sampledSynth;
  881.     }
  882.     return(currSynth);
  883. }
  884.  
  885. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  886. /*
  887. This routine will cruise through the given snd resource  It will locate
  888. the sound data, if any, and return its type and offset into the resource.
  889. I prefer to return an offset instead of a pointer because I don't want
  890. to have the data locked in memory.  If I return an offset, the caller
  891. can decided when and if it wants the resource locked down to access the
  892. sound data.   The first step in finding this data is to determine if I'm
  893. looking at a format 1 or 2 type snd.  A type 2 is easy, but a type 1 will
  894. require me to find the number of snths specified and then to skip over
  895. each one including the init option.  Once this is done, I have a pointer
  896. to the number of commands in the snd.  When I've found the first one, I
  897. examine it to find out if it is a sound data command.  Being it's a sound
  898. resource, the command will also have its dataPointerFlag set.  Once I've
  899. found a command I'm looking for I return its type and offset, then get out
  900. of the do-while block.  Otherwise I go on to the next command.  All of this
  901. makes it possible to get the sound data for use as an instrument sound.
  902. Typically this will be a sampled sound.
  903. */
  904.  
  905. #pragma segment SoundUnit
  906. pascal long GetSndDataOffset(SndListHandle sndHandle, short *dataType, short *waveLength)
  907. {
  908.     Ptr                cruisePtr;
  909.     long            sndDataOffset;
  910.     short            synths;
  911.     short            howManyCmds;
  912.  
  913.     sndDataOffset = 0;                            // initialize to defaults
  914.     *dataType = kNoSynth;
  915.     *waveLength = 0;
  916.     if (sndHandle == nil)
  917.         return (sndDataOffset);                    // return no data
  918.  
  919.     if (*sndHandle != nil) {
  920.         if ((**sndHandle).format == firstSoundFormat) {
  921.             synths = (**sndHandle).numModifiers;
  922.             cruisePtr = (Ptr)&(**sndHandle).modifierPart;
  923.             cruisePtr += (sizeof(ModRef) * synths);
  924.         }
  925.         else
  926.             cruisePtr = (Ptr)&((**(Snd2ListHandle)sndHandle).numCommands);
  927.         howManyCmds = *(short *)cruisePtr;        // pointing at number of cmds
  928.         cruisePtr += sizeof(howManyCmds);
  929.  
  930.         // cruisePtr is now at the first sound command
  931.         // cruise all commands and find a soundCmd or bufferCmd
  932.         do {
  933.             switch (((SndCmdPtr)cruisePtr)->cmd) {
  934.  
  935.                 case soundCmd | dataOffsetFlag:
  936.                 case bufferCmd | dataOffsetFlag:
  937.                     *dataType = sampledSynth;
  938.                     sndDataOffset = ((SndCmdPtr)cruisePtr)->param2;
  939.                     howManyCmds = 0;            // done, get out of loop
  940.                     break;
  941.  
  942.                 case waveTableCmd | dataOffsetFlag:
  943.                     *dataType = waveTableSynth;
  944.                     *waveLength = ((SndCmdPtr)cruisePtr)->param1;
  945.                     sndDataOffset = ((SndCmdPtr)cruisePtr)->param2;
  946.                     howManyCmds = 0;            // done, get out of loop
  947.                     break;
  948.  
  949.                 default:                        // catch any other type of cmd
  950.                     cruisePtr += sizeof(SndCommand);
  951.                     howManyCmds -= 1;
  952.                     break;
  953.             }
  954.         } while (howManyCmds >= 1);                // done with all the commands
  955.     }
  956.     return(sndDataOffset);
  957. }
  958.  
  959. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  960. /*
  961. VERSION 1.1:  Check the given sound header as being supported by the
  962. running Sound Manager.  The encode fields of the header are tested.
  963. The standard encode always works.  The MACE compressed sound will only work if
  964. MACE is present.  A MACE sound can also be a stereo sound, which will only
  965. work on stereo hardware.  The expanded sound is for a stereo sound and/or
  966. 16bit samples and is only supported by Sound Manager 2.0 or later.
  967. If the sound is a stereo sound, then it requires stereo support.
  968.  
  969. VERSION 1.2: Support for Sound Manager 3 and beyond. Any compressed
  970. sound may work if Sound Manager 3.0 or later is present.
  971. */
  972.  
  973. #pragma segment SoundUnit
  974. Boolean SupportedSH(SoundHeaderPtr sndPtr)
  975. {
  976.     Boolean result;
  977.  
  978.     result = false;
  979.     switch (sndPtr->encode)
  980.     {
  981.  
  982.         case stdSH:
  983.             result = true;
  984.             break;
  985.  
  986.         case cmpSH:
  987.             if (gSoundMgrVersion < 3)
  988.             {
  989.                 //Sound Manager 2.0 will only support MACE compressed sound
  990.                 //and only stereo sound on stereo machines.
  991.  
  992.                 if (    (((CmpSoundHeaderPtr)sndPtr)->snthID == MACE3snthID)
  993.                      || (((CmpSoundHeaderPtr)sndPtr)->snthID == MACE6snthID) )
  994.                 {
  995.                     if (((CmpSoundHeaderPtr)sndPtr)->numChannels == 1)
  996.                         result = true;
  997.                     else
  998.                         result = HasStereoSupport();
  999.                 }
  1000.             }
  1001.             else
  1002.                 result = true;                //Sound Manager 3 and later does it all
  1003.             break;
  1004.  
  1005.         case extSH:            // first check for 8 bit sounds
  1006.             if (gSoundMgrVersion < 3)
  1007.             {
  1008.  
  1009.                 if (((ExtSoundHeaderPtr)sndPtr)->sampleSize == 8)
  1010.                 {
  1011.                     if (((ExtSoundHeaderPtr)sndPtr)->numChannels == 1)
  1012.                         result = true;                    // it's a mono sound, no problem
  1013.                     else
  1014.                          result = HasStereoSupport();    // only if we have stereo
  1015.                  }
  1016.                  else
  1017.                      result = false;                        // non-8 bit not allowed
  1018.             }
  1019.             else        // must be a 16 bit sound, Sound Manager 3 and later does it all
  1020.                 result = true;
  1021.             break;
  1022.  
  1023.     }
  1024.     return (result);
  1025. }
  1026.  
  1027. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1028. /*
  1029. Given a channel and sampled sound resource, this routine will install the
  1030. sound into the channel for use as an instrument.  This allows an
  1031. application to send freqDurationCmds to the channel and play a melody.  If I sent
  1032. a bufferCmd instead of the soundCmd, the Sound Manager would play the
  1033. sampled sound.  This is basically what _SndPlay would do with a format 2
  1034. snd.  I insure that I am using only the proper buffer format having the
  1035. standard encode option.  If I were to support compressed sounds, I would
  1036. have to call the MACE synthesizers to expand the buffer before I can use
  1037. it as an instrument.  If I don't get a sampled sound of standard encoding
  1038. I'll return a bad format error.  I use _SndDoImmediate to get the sound
  1039. installed because I don't want this command to be queued.
  1040.  
  1041. VERSION 1.1:  Check for a supported sound header.
  1042. */
  1043.  
  1044. OSErr InstallSampleSnd(ChanInfoPtr info, SndListHandle sndHandle)
  1045. {
  1046.     SndCommand        theCmd;
  1047.     SoundHeaderPtr    dataPtr;
  1048.     long            dataOffset;
  1049.     short            sndDataType;
  1050.     short            ignore;
  1051.     OSErr            theErr;
  1052.  
  1053.     theErr = HoldSnd(sndHandle);
  1054.     if (theErr == noErr) {
  1055.         dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1056.         if (sndDataType == sampledSynth) {
  1057.             dataPtr = (SoundHeaderPtr)((long)(*sndHandle) + dataOffset);
  1058.             if (stdSH == dataPtr->encode) {
  1059.                 theCmd.cmd = soundCmd;
  1060.                 theCmd.param1 = 0;
  1061.                 theCmd.param2 = (long)dataPtr;
  1062.                 info->dataHandle = sndHandle;
  1063.                 theErr = SndDoImmediate(info->chan, &theCmd);
  1064.             } else
  1065.                 theErr = badFormat;                            //return a bad format error
  1066.         } else
  1067.             theErr = badFormat;                                //return a bad format error
  1068.         if (theErr != noErr) {
  1069.             HUnlock((Handle)sndHandle);                        //and free up the resource
  1070.             HPurge((Handle)sndHandle);
  1071.         }
  1072.     }
  1073.     return (theErr);
  1074. }
  1075.  
  1076. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1077. /*
  1078. This routine will create a sampled sound channel using the INIT option
  1079. given.  Typically this will be 0.  In any case with System 6.0x this
  1080. option is ignored by the sampled sound synthesizer.  The given sound
  1081. resource will be installed into the channel for use as an instrument.
  1082.  
  1083. WARNING: If the application does not want an instrument sound, then the
  1084. sndInstrument handle MUST be passed in as nil.
  1085.  
  1086. BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
  1087. a Memory Manager error when allocating its internal buffer.  There is a
  1088. call to NewPtr(1316)and if a nil is returned, the Sound Manager will
  1089. write randomly to low memory.  This can occur when calling _SysBeep under
  1090. low memory conditions.  Also, this pointer is allocated into the
  1091. application's heap instead of the system's.
  1092.  
  1093. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1094. supporting Sound Manager 2.0 or later.
  1095. */
  1096.  
  1097. #pragma segment SoundUnit
  1098. pascal OSErr GetSampleChan(SndChannelPtr *sampleChan, long init, SndListHandle sndInstrument)
  1099. {
  1100.     OSErr theErr;
  1101.  
  1102.     FreeAllChans();
  1103.     theErr = SndNewChannel(&(gChanInfo[0].chan), sampledSynth,
  1104.                             init, GetRoutineAddress(DoCallBack));
  1105.     if (theErr == noErr) {
  1106.         gChanInfo[0].chanType = sampledSynth;
  1107.         theErr = InstallSampleSnd(&gChanInfo[0], sndInstrument);
  1108.     }
  1109.     if (theErr != noErr)
  1110.         FreeAllChans();
  1111.     *sampleChan = gChanInfo[0].chan;
  1112.     return (theErr);
  1113. }
  1114.  
  1115. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1116. /*
  1117. Only supported by Sound Manager 2.0 and later
  1118.  
  1119. */
  1120.  
  1121. #pragma segment SoundUnit
  1122. pascal OSErr Get4SampleInstruments(SndChannelPtr *sampleChan1, SndChannelPtr *sampleChan2,
  1123.                                 SndChannelPtr *sampleChan3, SndChannelPtr *sampleChan4,
  1124.                                 SndListHandle sndInstrument1, SndListHandle sndInstrument2,
  1125.                                 SndListHandle sndInstrument3, SndListHandle sndInstrument4)
  1126. {
  1127.     OSErr theErr;
  1128.  
  1129.     FreeAllChans();
  1130.  
  1131.     theErr = SndNewChannel(&(gChanInfo[0].chan), sampledSynth,
  1132.                             kInitNone, GetRoutineAddress(DoCallBack));
  1133.     if (theErr == noErr) {
  1134.         gChanInfo[0].chanType = sampledSynth;
  1135.         theErr = InstallSampleSnd(&gChanInfo[0], sndInstrument1);
  1136.         if (theErr == noErr) {
  1137.             theErr = SndNewChannel(&(gChanInfo[1].chan), sampledSynth,
  1138.                                     kInitNone, GetRoutineAddress(DoCallBack));
  1139.             if (theErr == noErr) {
  1140.                 gChanInfo[1].chanType = sampledSynth;
  1141.                 theErr = InstallSampleSnd(&gChanInfo[1], sndInstrument2);
  1142.                 if (theErr == noErr) {
  1143.                     theErr = SndNewChannel(&(gChanInfo[2].chan), sampledSynth,
  1144.                                             kInitNone, GetRoutineAddress(DoCallBack));
  1145.                     if (theErr == noErr) {
  1146.                         gChanInfo[2].chanType = sampledSynth;
  1147.                         theErr = InstallSampleSnd(&gChanInfo[2], sndInstrument3);
  1148.                         if (theErr == noErr) {
  1149.                             theErr = SndNewChannel(&(gChanInfo[3].chan), sampledSynth,
  1150.                                                     kInitNone, GetRoutineAddress(DoCallBack));
  1151.                             if (theErr == noErr) {
  1152.                                 gChanInfo[3].chanType = sampledSynth;
  1153.                                 theErr = InstallSampleSnd(&gChanInfo[3], sndInstrument4);
  1154.                             }
  1155.                         }
  1156.                     }
  1157.                 }
  1158.             }
  1159.         }
  1160.     }
  1161.  
  1162.     if (theErr == noErr)
  1163.     {
  1164.         *sampleChan1 = gChanInfo[0].chan;
  1165.         *sampleChan2 = gChanInfo[1].chan;
  1166.         *sampleChan3 = gChanInfo[2].chan;
  1167.         *sampleChan4 = gChanInfo[3].chan;
  1168.     }
  1169.     else
  1170.         FreeAllChans();
  1171.     return (theErr);
  1172. }
  1173.  
  1174. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1175. /*
  1176. Given a channel and pointer to a wave table, this will install the wave
  1177. for use as an instrument into the channel.  If I find the application
  1178. giving me a nil pointer, I'll return an error.  I use _SndDoImmediate
  1179. to get the sound installed because I don't want this to be queued.
  1180. */
  1181.  
  1182. #pragma segment SoundUnit
  1183. pascal OSErr InstallWave(SndChannelPtr waveChan, Ptr aWavePtr, short waveLength)
  1184. {
  1185.     SndCommand theCmd;
  1186.     OSErr      result;
  1187.  
  1188.     if (aWavePtr != nil) {
  1189.         theCmd.cmd = waveTableCmd;
  1190.         theCmd.param1 = waveLength;
  1191.         theCmd.param2 = (long)aWavePtr;
  1192.         result = SndDoImmediate(waveChan, &theCmd);
  1193.     }
  1194.     else
  1195.         result = memPCErr;                        // Pointer Check failed
  1196.     return (result);
  1197. }
  1198.  
  1199. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1200. /*
  1201. NewWaveChan creates a new wave table channel and sets myChan to point
  1202. to it.  If any error occurs, the error code is returned as a function
  1203. result.
  1204. */
  1205.  
  1206. #pragma segment SoundUnit
  1207. OSErr NewWaveChan(ChanInfoPtr info, short init)
  1208. {
  1209.     OSErr theErr;
  1210.  
  1211.     theErr = SndNewChannel(&(info->chan), waveTableSynth,
  1212.                             init, GetRoutineAddress(DoCallBack));
  1213.     if (theErr == noErr)
  1214.         info->chanType = waveTableSynth;
  1215.  
  1216.     return (theErr);
  1217. }
  1218.  
  1219. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1220. /*
  1221. This will return four wave table channels with their waves installed.
  1222. This must be done before calling ChanAvailable.  Otherwise that test will
  1223. fail.  If I cannot obtain all four wave channels I will dispose of the
  1224. ones I did get before returning the error.  This routine expects to find
  1225. four wave table pointers, or it will fail.
  1226.  
  1227. Versions 1.2: After calling NewWaveChan, we do not need to call ChanAvailable
  1228. since we know that Sound Manager 2 or later will not do the wrong thing.
  1229. */
  1230.  
  1231. #pragma segment SoundUnit
  1232. pascal OSErr GetWaveChans(SndChannelPtr *waveChan1, SndChannelPtr *waveChan2,
  1233.                             SndChannelPtr *waveChan3, SndChannelPtr *waveChan4)
  1234. {
  1235.     OSErr theErr;
  1236.  
  1237.     FreeAllChans();
  1238.  
  1239.     theErr = NewWaveChan(&gChanInfo[0], waveInitChannel0);
  1240.     if (theErr == noErr)
  1241.     {
  1242.         theErr = NewWaveChan(&gChanInfo[1], waveInitChannel1);
  1243.         if (theErr == noErr)
  1244.         {
  1245.             theErr = NewWaveChan(&gChanInfo[2], waveInitChannel2);
  1246.             if (theErr == noErr)
  1247.                 theErr = NewWaveChan(&gChanInfo[3], waveInitChannel3);
  1248.         }
  1249.     }
  1250.  
  1251.     if (theErr != noErr)
  1252.         FreeAllChans();                            // we didn't make it
  1253.     else {
  1254.         *waveChan1 = gChanInfo[0].chan;
  1255.         *waveChan2 = gChanInfo[1].chan;
  1256.         *waveChan3 = gChanInfo[2].chan;
  1257.         *waveChan4 = gChanInfo[3].chan;
  1258.     }
  1259.     return (theErr);
  1260. }
  1261.  
  1262. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1263. /*
  1264. This will create a channel for the square wave synthesizer.  There
  1265. are no INIT options used by this synthesizer, but I will set the timbre
  1266. to adjust the tone quality.
  1267.  
  1268. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1269. supporting Sound Manager 2.0 or later.
  1270. */
  1271.  
  1272. #pragma segment Sound
  1273. pascal OSErr GetSquareWaveChan(SndChannelPtr *squareChan, short timbre)
  1274. {
  1275.     OSErr theErr;
  1276.  
  1277.     FreeAllChans();
  1278.     theErr = SndNewChannel(&(gChanInfo[0].chan), squareWaveSynth,
  1279.                                 kInitNone, GetRoutineAddress(DoCallBack));
  1280.     if (theErr == noErr) {
  1281.         gChanInfo[0].chanType = squareWaveSynth;
  1282.         theErr = SetSquareWaveTimbre(gChanInfo[0].chan, timbre, !kWait);
  1283.     }
  1284.     if (theErr != noErr)
  1285.         FreeAllChans();
  1286.     *squareChan = gChanInfo[0].chan;
  1287.     return (theErr);
  1288. }
  1289.  
  1290. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1291. /*
  1292. This is the routine to create a channel that isn't associated with any
  1293. synthesizer.  Why? Because if you wanted to use _SndPlay asynchronously
  1294. you need to get such a channel.
  1295.  
  1296. BUG NOTE: Do not use a channel already associated to a snth with
  1297. _SndPlay.  This causes the Sound Manager to install a second copy of the
  1298. same snth.
  1299.  
  1300. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1301. supporting Sound Manager 2.0 or later.
  1302. */
  1303.  
  1304. #pragma segment SoundUnit
  1305. pascal OSErr GetNoSynthChan(SndChannelPtr *chan)
  1306. {
  1307.     OSErr theErr;
  1308.  
  1309.     FreeAllChans();
  1310.     theErr = SndNewChannel(&(gChanInfo[0].chan), kNoSynth,
  1311.                                 kInitNone, GetRoutineAddress(DoCallBack));
  1312.     if (theErr == noErr)
  1313.         gChanInfo[0].chanType = kNoSynth;
  1314.     if (theErr != noErr)
  1315.         FreeAllChans();
  1316.     *chan = gChanInfo[0].chan;
  1317.     return (theErr);
  1318. }
  1319.  
  1320. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1321. /*
  1322. This routine will use the given channel and snd resource with _SndPlay.
  1323. This is used to play a sound, which is a series of sound commands commonly
  1324. referred to as a sequence.  First thing I do is make sure the song fits in
  1325. memory.  _SndPlay will lock this resource in memory and then pump the snd
  1326. for all of its worth.  I am calling it asynchronously, and if I was using
  1327. a snd that contained sound data I wouldn't mark the snd as being
  1328. purgeable.  But in this case, _SndPlay will be done with the snd as soon
  1329. as it returns because it copied all of the commands into the channel.
  1330. (There's no data associated with a sequence, just commands.) _SndPlay
  1331. will not return until it has done so.  After _SndPlay I need to work
  1332. around a bug in the freqDurationCmd.  The last thing to do is to send a
  1333. callBackCmd to signal me that the channel has completed.  If any Sound
  1334. Manager errors are encountered, I return them to the application.  If the
  1335. application passed me a nil snd handle, I'll return an error.
  1336.  
  1337. WARNING: Make sure you are using a snd that only has note type commands
  1338. in it and not something such as a bufferCmd.
  1339.  
  1340. BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
  1341. The note will continue to sound, looping forever, until a quietCmd is sent
  1342. or the channel is disposed of.  To prevent unwanted looping, I send a
  1343. quietCmd after all notes.  Also read a related bug note when disposing of
  1344. channels in the routine FreeChan().
  1345. */
  1346.  
  1347. #pragma segment SoundUnit
  1348. pascal OSErr PlaySong(SndChannelPtr chan, SndListHandle sndSong)
  1349. {
  1350.     OSErr theErr;
  1351.  
  1352.     theErr = SndDataAvailable(sndSong);                // get the data loaded
  1353.     if (theErr == noErr) {
  1354.         theErr = SndPlay(chan, sndSong, kSMAsynch);    // pump the sound
  1355.         HUnlock((Handle)sndSong);
  1356.         HPurge((Handle)sndSong);
  1357.         if (theErr == noErr) {
  1358.             theErr = SendQuiet(chan, kWait);        // work around bug
  1359.             if (theErr == noErr)
  1360.                 theErr = SoundComplete(chan);
  1361.         }
  1362.         else
  1363.             theErr = nilHandleErr;                    // snd data was not available
  1364.     }
  1365.     if (theErr != noErr)
  1366.         FreeAllChans();
  1367.     return (theErr);
  1368. }
  1369.  
  1370. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1371. /*
  1372. This is used to send a syncCmd to a channel and causes the other channels
  1373. that are being held by a synchCmd to be released.  Of course, this assumes
  1374. the application has already called SynchChans.  _SndDoImmediate is used
  1375. to get the command directly to the synthesizer bypassing the queue.
  1376.  
  1377. BUG NOTE: I've found that immediately clearing the channels and starting
  1378. new ones may cause the channels to startup playing out of synch?  This
  1379. happens while disposing the wave channels and starting them immediately.
  1380. */
  1381.  
  1382. #pragma segment SoundUnit
  1383. OSErr ReleaseSynch(SndChannelPtr chan)
  1384. {
  1385.     SndCommand theCmd;
  1386.  
  1387.     theCmd.cmd = syncCmd;
  1388.     theCmd.param1 = 1;
  1389.     theCmd.param2 = kSyncID;
  1390.     return (SndDoImmediate(chan, &theCmd));
  1391. }
  1392.  
  1393. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1394. /*
  1395. This is a utility routine for SynchChans.  It sends a syncCmd command
  1396. to the channel specified by chan using the count parameter specified
  1397. by count.  Synch1Chan returns whatever error that SndDoImmediate returns.
  1398. */
  1399.  
  1400. #pragma segment SoundUnit
  1401. OSErr Synch1Chan(SndChannelPtr chan, short count)
  1402. {
  1403.     SndCommand theCmd;
  1404.  
  1405.     theCmd.cmd = syncCmd;
  1406.     theCmd.param1 = count;
  1407.     theCmd.param2 = kSyncID;
  1408.     return (SndDoImmediate(chan, &theCmd));
  1409. }
  1410.  
  1411. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1412. /*
  1413. This is used to synchronize four wave table channels.  By first sending
  1414. the synchCmd, I can send a sequence of other commands to the channel and
  1415. not have the channel attempt to start processing any of them.  That is until
  1416. another synchCmd is sent causing all of the previous synchCmd's counter
  1417. to be decremented.  After getting all the channels in synch and sending
  1418. the sequence of further commands, then use the ReleaseSynch routine to
  1419. start all of the channels processing their respective queues.
  1420. _SndDoImmediate is used to get the command directly to the synthesizer
  1421. bypassing the queue.
  1422. */
  1423.  
  1424. #pragma segment SoundUnit
  1425. OSErr SynchChans(SndChannelPtr chan1, SndChannelPtr chan2,
  1426.                         SndChannelPtr chan3, SndChannelPtr chan4)
  1427. {
  1428.     OSErr theErr;
  1429.  
  1430.     theErr = Synch1Chan(chan4, 5);
  1431.     if (theErr == noErr) {
  1432.         theErr = Synch1Chan(chan3, 4);
  1433.         if (theErr == noErr) {
  1434.             theErr = Synch1Chan(chan2, 3);
  1435.             if (theErr == noErr)
  1436.                 theErr = Synch1Chan(chan1, 2);
  1437.         }
  1438.     }
  1439.     return (theErr);
  1440. }
  1441.  
  1442. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1443. /*
  1444. In order to synchronize channels, the synchCmd is needed.  Once all of the
  1445. song has been sent into each channel, a final synchCmd is issued to
  1446. release them.  Don't send more commands into a channel that it can hold at
  1447. one time while the channel is in synch mode.
  1448. */
  1449.  
  1450. #pragma segment SoundUnit
  1451. pascal OSErr Play4ChanSongs(SndChannelPtr chan1, SndChannelPtr chan2,
  1452.                             SndChannelPtr chan3, SndChannelPtr chan4,
  1453.                             SndListHandle song1, SndListHandle song2,
  1454.                             SndListHandle song3, SndListHandle song4)
  1455. {
  1456.     OSErr theErr;
  1457.  
  1458.     theErr = SynchChans(chan1, chan2, chan3, chan4);
  1459.     if (theErr == noErr) {
  1460.         theErr = PlaySong(chan1, song1);
  1461.         if (theErr == noErr) {
  1462.             theErr = PlaySong(chan2, song2);
  1463.             if (theErr == noErr) {
  1464.                 theErr = PlaySong(chan3, song3);
  1465.                 if (theErr == noErr) {
  1466.                     theErr = PlaySong(chan4, song4);
  1467.                     if (theErr == noErr)
  1468.                         theErr = ReleaseSynch(chan1);
  1469.                 }
  1470.             }
  1471.         }
  1472.     }
  1473.  
  1474.     return (theErr);
  1475. }
  1476.  
  1477. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1478. /*
  1479. WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE.  I've provided
  1480. this routine because people have asked me how HyperCard performs its PLAY
  1481. command and why their HyperCard sounds do not sound right using the
  1482. _SndPlay routine.  The correct answer is that _SndPlay plays the sound
  1483. correctly.  HyperCard is attempting to change the frequency by adjusting
  1484. the sample rate.  This is NOT the correct approach.  Define the sound
  1485. buffer as it should be played.  _SndPlay plays the sound as it is defined.
  1486. If the result from _SndPlay is not what you want, then it is the sample
  1487. that is incorrect and should be edited.  The sample rate is the rate at
  1488. which the sound was recorded.  If you didn't record it, then how do you
  1489. know what's the correct rate?  Set the baseFrequency to the note that was
  1490. recorded.  If you recorded middle C at 22k, then the rate is 22k and the
  1491. baseFrequency is middle C.  HyperCard is incorrect in using the sample rate as
  1492. the frequency of the sound.  Furthermore, using this technique of
  1493. calculating a new sample rate can introduce errors.  The resulting sample
  1494. rate will not be the proper pitch.  Also, the sample rate for high pitches
  1495. will be very inaccurate and impossible for the Mac to reproduce.  Such a
  1496. problem can happen if the given sample rate was 22k and is to be played
  1497. back at three octaves higher.  Even 44k samples transposed up a half
  1498. octave will fail.  Using the soundCmd and freqDurationCmd will not have this
  1499. problem.
  1500.  
  1501. Given a sound resource, this routine will play it in the manner that
  1502. HyperCard does.  HyperCard assumes that a sound is to be played at middle
  1503. C when the user does not specify a note value in the PLAY command.  I
  1504. don't know why. (What's middle C when I want to hear speech or the sound
  1505. of crickets?) At any rate(pun intended), I get a sampled sound channel.
  1506. I find the sound data offset in the resource, which has to be locked down
  1507. at this time.  Once I have the sound data, I get its original sample rate.
  1508. I have to calculate what a new sample rate would be based on its baseFrequency.
  1509. The baseFrequency is the note at which the sound was recorded.  I'm not sure
  1510. what this means to crickets, but if this is set to middle C then HyperCard
  1511. doesn't attempt to modify the sample rate. (If you're wondering how the
  1512. math works in this routine, buy a book on music theory.  I'm here to
  1513. provide Mac support.) Once I've adjusted the sample rate, I use the
  1514. bufferCmd to play it.  Then I restore the sound resource to its original
  1515. state.  If I didn't do this it would be possible that the resource was
  1516. still in memory the next time I use it having the adjusted sample rate.
  1517. This would cause me to incorrectly adjust it again.  Unlike HyperCard, I
  1518. can do this for both a format 1 and 2.
  1519.  
  1520. BUG NOTE: Do not call SANE while the Sound Manager is running.  Refer to
  1521. Tech Note #235.
  1522.  
  1523. VERSION 1.1:  Replaced the previous test of the sound header's encode
  1524. value.  The previous version was incorrect.  The bufferCmd will automatically
  1525. de-code a MACE compressed sound if MACE is available.  Otherwise, the sound
  1526. will not be able to be used.  So, a new routine is being used to check for
  1527. supported sound headers.  The conflict with the Sound Manager and SANE was
  1528. resolved in the new Sound Manager.  The Sound Manager no longers uses extended
  1529. numbers at interrupt level, and instead uses fixed math.
  1530. */
  1531.  
  1532. #pragma segment SoundUnit
  1533. pascal OSErr HyperSndPlay(SndListHandle sndHandle)
  1534. {
  1535.     SndCommand      theCmd;
  1536.     OSErr              theErr;
  1537.     long             dataOffset;
  1538.     short             sndDataType;
  1539.     short              ignore;
  1540.     SoundHeaderPtr    dataPtr;
  1541.     short              thePower;
  1542.     double_t        newRate;
  1543.     Fixed           oldRate;
  1544.  
  1545.     theErr = HoldSnd(sndHandle);
  1546.     if (theErr == noErr) {
  1547.         theErr = GetSampleChan(&(gChanInfo[0].chan), kInitNone, nil);
  1548.         gChanInfo[0].dataHandle = sndHandle;                  // so FreeAllChans can dispose of data
  1549.         if (theErr == noErr) {
  1550.             dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1551.             if (sndDataType == sampledSynth) {
  1552.                 dataPtr = (SoundHeaderPtr)((long)*sndHandle + dataOffset);
  1553.  
  1554.                 if (SupportedSH(dataPtr)) {
  1555.                     oldRate = dataPtr->sampleRate;      // save original sample rate
  1556.                     if (dataPtr->baseFrequency != kMiddleC) {
  1557.                         if (dataPtr->sampleRate > (SHRT_MAX << 16)) // large positive number
  1558.                             newRate = Fix2X(dataPtr->sampleRate - (SHRT_MAX << 16))
  1559.                                             + (double_t)SHRT_MAX;
  1560.                         else
  1561.                             newRate = Fix2X(dataPtr->sampleRate);
  1562.                         thePower = (kMiddleC) - (dataPtr->baseFrequency);
  1563.                         dataPtr->sampleRate = X2Fix(newRate *
  1564.                                             pow((double_t)twelfthRootTwo, (double_t)thePower));
  1565.                     }
  1566.                     theCmd.cmd = bufferCmd;
  1567.                     theCmd.param1 = 0;
  1568.                     theCmd.param2 = (long)dataPtr;
  1569.                     theErr = SndDoImmediate(gChanInfo[0].chan, &theCmd);
  1570.                     if (theErr == noErr)
  1571.                         theErr = SoundComplete(gChanInfo[0].chan);
  1572.                     dataPtr->sampleRate = oldRate;      // restore original sample rate
  1573.                 } else
  1574.                     theErr = badFormat;                    //not SupportedSH
  1575.             } else
  1576.                 theErr = badFormat;                        //sndDataType not sampledSynth
  1577.         }
  1578.     }
  1579.     if (theErr != noErr)
  1580.         FreeAllChans();
  1581.     return (theErr);
  1582. }
  1583.  
  1584. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1585. /*
  1586. Given a sound resource, this routine will call _SndPlay.  The snd must
  1587. be either a format 2 or format 1 that contains snth information.
  1588. Using _SndPlay asynchronously requires us to lock the snd prior to
  1589. calling the trap.  The reason being is _SndPlay calls HSetState as soon
  1590. as the trap exits, and restores the handle to whatever is was just before
  1591. the call.  This would be bad when using the sound asynchronously.  If the
  1592. sound being passed in happens to be a compressed sound created with MACE,
  1593. it will "do the right thing."  If MACE isn't around the Sound Manager
  1594. will pretend to play a sound but nothing will be heard.
  1595.  
  1596. BUG NOTE:  The sampled sound synthesizer in System 6.0x does not check for
  1597. a Memory Manager error when allocating its internal buffer.  There is a
  1598. call to NewPtr(1316)and if a nil is return, the Sound Manager will write
  1599. randomly to memory.  Also, the pointer is allocated into the application's
  1600. heap instead of the system's.
  1601.  
  1602. BUG NOTE:  _SndPlay when using System 6.0.4 and a sampled sound will send
  1603. a bogus callBackCmd into the channel.  This will cause the user's call
  1604. back procedure to be called as soon as the sound has completed.  Refer
  1605. to the DoCallBack routine for details.
  1606.  
  1607. VERSION 1.1:  Add the check for the sound header being supported and
  1608. replaced the check of the snth information.  No synth information in the
  1609. snd is valid and would mean to play the snd using the squareWaveSynth.
  1610. */
  1611.  
  1612. #pragma segment SoundUnit
  1613. pascal OSErr AsynchSndPlay(SndListHandle sndHandle)
  1614. {
  1615.     SoundHeaderPtr dataPtr;
  1616.     OSErr theErr;
  1617.     long dataOffset;
  1618.     short sndDataType;
  1619.     short ignore;
  1620.  
  1621.     theErr = HoldSnd(sndHandle);                //hold on to the sound
  1622.     if (theErr == noErr) {
  1623.         theErr = GetNoSynthChan(&(gChanInfo[0].chan));
  1624.         gChanInfo[0].dataHandle = sndHandle;        //so FreeAllChans can dispose of data
  1625.         if (theErr == noErr) {
  1626.             gChanInfo[0].chanType = GetSynthInfo(sndHandle).modNumber;
  1627.             dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1628.             if (sndDataType == sampledSynth) {
  1629.                 dataPtr = (SoundHeaderPtr)((long)*sndHandle + dataOffset);
  1630.                 if ( !(SupportedSH(dataPtr)) )
  1631.                     theErr = badFormat;
  1632.             }
  1633.             if (theErr == noErr) {
  1634.                 theErr = SndPlay(gChanInfo[0].chan, sndHandle, kSMAsynch);
  1635.                 if (theErr == noErr)
  1636.                     theErr = SoundComplete(gChanInfo[0].chan);
  1637.             }
  1638.         }
  1639.     }
  1640.     if (theErr != noErr)
  1641.         FreeAllChans();
  1642.     return(theErr);
  1643. }
  1644.  
  1645.